package edu.bbu.commonproject.backend.server.services.thrift.communication;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.List;
import java.util.logging.Level;

import org.apache.thrift.TException;

import edu.bbu.commonproject.backend.logic.ServerBLL;
import edu.bbu.commonproject.backend.logic.exceptions.DataBaseAccessException;
import edu.bbu.commonproject.backend.logic.exceptions.DecryptingPasswordException;
import edu.bbu.commonproject.backend.logic.exceptions.DirectoryOperationException;
import edu.bbu.commonproject.backend.logic.exceptions.EmailSendingException;
import edu.bbu.commonproject.backend.logic.exceptions.FileOperationException;
import edu.bbu.commonproject.backend.logic.exceptions.FileTransferException;
import edu.bbu.commonproject.backend.logic.exceptions.UserNameExistsException;
import edu.bbu.commonproject.backend.logic.exceptions.WrongPasswordException;
import edu.bbu.commonproject.backend.logic.exceptions.WrongUserNameException;
import edu.bbu.commonproject.backend.server.model.DirectoryPOJO;
import edu.bbu.commonproject.backend.server.model.FilePOJO;
import edu.bbu.commonproject.backend.server.model.UserPOJO;
import edu.bbu.commonproject.backend.server.services.thrift.exceptions.TDatabaseErrorException;
import edu.bbu.commonproject.backend.server.services.thrift.exceptions.TDecryptErrorException;
import edu.bbu.commonproject.backend.server.services.thrift.exceptions.TDirectoryOperationException;
import edu.bbu.commonproject.backend.server.services.thrift.exceptions.TEmailSendingException;
import edu.bbu.commonproject.backend.server.services.thrift.exceptions.TFileOperationException;
import edu.bbu.commonproject.backend.server.services.thrift.exceptions.TWrongUserDataException;
import edu.bbu.commonproject.backend.server.services.thrift.utils.ThriftSocketFileObjectInputStreamReader;
import edu.bbu.commonproject.backend.utils.ServerLogger;

/**
 * The Thrift service handler. Here the Thrift listener will call the method, which has been requested by the client.
 */
public class ServiceHandlers implements ThriftService.Iface, SocketService.Iface
{
	/** The servers BLL. */
	private ServerBLL serverBLL;
	
	/**
	 * The constructor.
	 */
	public ServiceHandlers()
	{
		serverBLL = new ServerBLL();
	}
	
	/**
	 * Logging on the user.
	 */
	public void logonUser(ThriftUser thriftUser) throws TWrongUserDataException, TDatabaseErrorException, TException 
	{
		UserPOJO user = new UserPOJO(thriftUser.getUserName(), thriftUser.getUserPassword());
		
		try
		{
			serverBLL.loginUser(user);
		}
		catch (WrongPasswordException | WrongUserNameException e)
		{
			throw new TWrongUserDataException("Incorrent username or password.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}
	
	/**
	 * Registering the user.
	 */
	public void registerUser(ThriftUser thriftUser) throws TDecryptErrorException, TWrongUserDataException, TDatabaseErrorException, TEmailSendingException, TException
	{
		UserPOJO user = new UserPOJO(thriftUser.getUserName(), thriftUser.getUserPassword());
		
		try
		{
			serverBLL.registerUser(user);
		}
		catch (DecryptingPasswordException e)
		{
			throw new TDecryptErrorException("Error occured on password decrypting.");
		}
		catch (UserNameExistsException e)
		{
			throw new TWrongUserDataException("Username already in use.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
		catch (EmailSendingException e)
		{
			throw new TEmailSendingException("Error occured on email sending.");
		}
		
	}
	
	/**
	 * Change user password.
	 */
	public void changePassword(ThriftUser thriftUser, String newPassword) throws TWrongUserDataException, TDatabaseErrorException, TException
	{
		UserPOJO user = new UserPOJO(thriftUser.getUserName(), thriftUser.getUserPassword());
		
		try
		{
			serverBLL.changeUserPassword(user, newPassword);
		}
		catch (WrongUserNameException | WrongPasswordException e)
		{
			throw new TWrongUserDataException("Incorrent username of password.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}
	
	/**
	 * Change the users password if it was forgotten.
	 */
	public  void forgotPassword(ThriftUser thriftUser, String newPassword) throws TDecryptErrorException, TWrongUserDataException, TDatabaseErrorException, TEmailSendingException, TException
	{
		UserPOJO user = new UserPOJO(thriftUser.getUserName(), thriftUser.getUserPassword());
		
		try
		{
			serverBLL.forgotUserPassword(user, newPassword);
		}
		catch (DecryptingPasswordException e)
		{
			throw new TDecryptErrorException("Error occured on password decrypting.");
		}
		catch (WrongUserNameException e)
		{
			throw new TWrongUserDataException("Incorrent username of password.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
		catch (EmailSendingException e)
		{
			throw new TEmailSendingException("Error occured on email sending.");
		}
	}

	/**
	 * Delete the user.
	 */
	public void deleteUser(ThriftUser thriftUser) throws TWrongUserDataException, TDatabaseErrorException, TException
	{
		UserPOJO user = new UserPOJO(thriftUser.getUserName(), thriftUser.getUserPassword());
		
		try
		{
			serverBLL.deleteUser(user);
		}
		catch (WrongUserNameException | WrongPasswordException e)
		{
			throw new TWrongUserDataException("Incorrent username of password.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}
	
	/**
	 * Delete a file.
	 */
	public void deleteFile(ThriftFile thriftFile) throws TFileOperationException, TDatabaseErrorException, TException
	{
		FilePOJO filePOJO = new FilePOJO();
		filePOJO.setOwner(thriftFile.getOwner());
		filePOJO.setFileName(thriftFile.getFileName());
		filePOJO.setVersion(thriftFile.getFileVersion());
		filePOJO.setDirectory(thriftFile.getDirectoryName());
		
		try
		{
			serverBLL.deleteFile(filePOJO);
		}
		catch (FileOperationException e)
		{
			throw new TFileOperationException("File not found.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
		
		
	}
	
	/**
	 * Delete all file versions.
	 */
	public void deleteAllFileVersions(ThriftFile thriftFile) throws TFileOperationException, TDatabaseErrorException, TException
	{
		FilePOJO filePOJO = new FilePOJO();
		filePOJO.setOwner(thriftFile.getOwner());
		filePOJO.setFileName(thriftFile.getFileName());
		filePOJO.setDirectory(thriftFile.getDirectoryName());
		
		try
		{
			serverBLL.deleteAllFileVersions(filePOJO);
		}
		catch (FileOperationException e)
		{
			throw new TFileOperationException("File not found.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}

	/**
	 * Getting the last file version.
	 */
	public int getLastFileVersion(ThriftFile thriftFile) throws TFileOperationException, TDatabaseErrorException, TException
	{
		FilePOJO filePOJO = new FilePOJO();
		filePOJO.setOwner(thriftFile.getOwner());
		filePOJO.setFileName(thriftFile.getFileName());
		filePOJO.setDirectory(thriftFile.getDirectoryName());
		
		try
		{
			return serverBLL.getLastVersionNumber(filePOJO);
		}
		catch (FileOperationException e)
		{
			throw new TFileOperationException("File not found.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}
	
	/**
	 * Getting the file version numbers.
	 */
	public List<Integer> getFileVersionNumbers(ThriftFile thriftFile) throws TFileOperationException, TDatabaseErrorException, TException
	{
		FilePOJO filePOJO = new FilePOJO();
		filePOJO.setOwner(thriftFile.getOwner());
		filePOJO.setFileName(thriftFile.getFileName());
		filePOJO.setDirectory(thriftFile.getDirectoryName());
		
		try
		{
			return serverBLL.getFileVersionNumbers(filePOJO);
		}
		catch (FileOperationException e)
		{
			throw new TFileOperationException("File not found.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}
	
	/**
	 * Getting the files from a directory.
	 */
	public List<String> getFilesFromDirectory(ThriftDirectory thriftDirectory) throws TDatabaseErrorException, TException
	{
		DirectoryPOJO directoryPOJO = new DirectoryPOJO();
		directoryPOJO.setOwner(thriftDirectory.getOwner());
		directoryPOJO.setDirectoryName(thriftDirectory.getDirectoryName());
		directoryPOJO.setParrentDirectory(thriftDirectory.getParrentDirectory());
		
		try
		{
			return serverBLL.getFilesFromDirectory(directoryPOJO);
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}
	
	/**
	 * Getting the file size.
	 */
	public long getFileSize(ThriftFile thriftFile) throws TFileOperationException, TDatabaseErrorException, TException
	{
		FilePOJO filePOJO = new FilePOJO();
		filePOJO.setOwner(thriftFile.getOwner());
		filePOJO.setFileName(thriftFile.getFileName());
		filePOJO.setVersion(thriftFile.getFileVersion());
		filePOJO.setDirectory(thriftFile.getDirectoryName());
		
		try
		{
			return serverBLL.getFileSize(filePOJO);
		}
		catch (FileOperationException e)
		{
			throw new TFileOperationException("File not found.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}

	@Override
	public long getFileUploadDate(ThriftFile thriftFile) throws TFileOperationException, TDatabaseErrorException, TException 
	{
		FilePOJO filePOJO = new FilePOJO();
		filePOJO.setOwner(thriftFile.getOwner());
		filePOJO.setFileName(thriftFile.getFileName());
		filePOJO.setVersion(thriftFile.getFileVersion());
		filePOJO.setDirectory(thriftFile.getDirectoryName());
		
		try
		{
			return serverBLL.getFileUploadDate(filePOJO);
		}
		catch (FileOperationException e)
		{
			throw new TFileOperationException("File not found.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}
	
	/**
	 * Creating a new directory.
	 */
	public void createDirectory(ThriftDirectory thriftDirectory) throws TDirectoryOperationException, TDatabaseErrorException, TException
	{
		DirectoryPOJO directoryPOJO = new DirectoryPOJO();
		directoryPOJO.setOwner(thriftDirectory.getOwner());
		directoryPOJO.setParrentDirectory(thriftDirectory.getParrentDirectory());
		directoryPOJO.setDirectoryName(thriftDirectory.getDirectoryName());
		
		try
		{
			serverBLL.createDirectory(directoryPOJO);
		}
		catch (DirectoryOperationException e)
		{
			throw new TDirectoryOperationException("Error while creating directory.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}		
	}

	/**
	 * Deleting a directory.
	 */
	public void deleteDirectory(ThriftDirectory thriftDirectory) throws TDirectoryOperationException, TDatabaseErrorException, TException
	{
		DirectoryPOJO directoryPOJO = new DirectoryPOJO();
		directoryPOJO.setOwner(thriftDirectory.getOwner());
		directoryPOJO.setParrentDirectory(thriftDirectory.getParrentDirectory());
		directoryPOJO.setDirectoryName(thriftDirectory.getDirectoryName());
	
		try
		{
			serverBLL.deleteDirectory(directoryPOJO);
		}
		catch (DirectoryOperationException e)
		{
			throw new TDirectoryOperationException("Error while deleting directory.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}

	/**
	 * Getting the directories from a directory.
	 */
	public List<String> getDirectoriesFromDirectory(ThriftDirectory thriftDirectory) throws TDatabaseErrorException, TException
	{
		DirectoryPOJO directoryPOJO = new DirectoryPOJO();
		directoryPOJO.setOwner(thriftDirectory.getOwner());
		directoryPOJO.setParrentDirectory(thriftDirectory.getParrentDirectory());
		directoryPOJO.setDirectoryName(thriftDirectory.getDirectoryName());
	
		try
		{
			return serverBLL.getDirectoriesFromDirectory(directoryPOJO);
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}

	/**
	 * Getting the base directory.
	 */
	public ThriftDirectory getBaseDirectory(String owner) throws TException
	{
		 DirectoryPOJO directoryPOJO = serverBLL.getBaseDirectory(owner);
		 
		 ThriftDirectory thriftDirectory = new ThriftDirectory();
		 thriftDirectory.setDirectoryName(directoryPOJO.getDirectoryName());
		 thriftDirectory.setParrentDirectory(directoryPOJO.getParrentDirectory());
		 thriftDirectory.setOwner(directoryPOJO.getOwner());
		 
		 return thriftDirectory;
	}
	
	/**
	 * Setting the synchronize directory.
	 */
	public void setSyncDirectory(ThriftDirectory thriftDirectory) throws TDatabaseErrorException, TException
	{
		 DirectoryPOJO directoryPOJO = new DirectoryPOJO();
		 directoryPOJO.setOwner(thriftDirectory.getOwner());
		 directoryPOJO.setDirectoryName(thriftDirectory.getDirectoryName());
		 directoryPOJO.setParrentDirectory(thriftDirectory.getParrentDirectory());
		 
		 try
		{
			serverBLL.setSyncDirectory(directoryPOJO);
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}

	/**
	 * Getting the synchronize directory.
	 */
	public ThriftDirectory getSyncDirectory(String owner) throws TDatabaseErrorException, TWrongUserDataException, TException
	{
		try
		{
			DirectoryPOJO directoryPOJO = serverBLL.getSyncDirectory(owner);
			
			ThriftDirectory thriftDirectory = new ThriftDirectory();
			thriftDirectory.setDirectoryName(directoryPOJO.getDirectoryName());
			thriftDirectory.setParrentDirectory(directoryPOJO.getParrentDirectory());
			thriftDirectory.setOwner(directoryPOJO.getOwner());
			 
			return thriftDirectory;
			
		}
		catch (WrongUserNameException e)
		{
			throw new TWrongUserDataException("Username not exist.");
		}
		catch (DataBaseAccessException e)
		{
			throw new TDatabaseErrorException("Error occured on database.");
		}
	}	
	
	/**
	 * Uploading or downloading a file.
	 */
	public void handleSocketRequest(Socket socket)
	{
		InputStream socketInputStream;
		OutputStream socketOutputStream;
		
		try
		{
			socketInputStream = socket.getInputStream();
			socketOutputStream = socket.getOutputStream();
		}
		catch (IOException e)
		{
			try
			{
				socket.close();
			}
			catch (IOException ex)
			{
				ServerLogger.log(Level.SEVERE, "File transfer failed.");
			}
			
			return;
		}
		
		SocketFile fileMessage;
		
		try
		{
			ThriftSocketFileObjectInputStreamReader objectReader = new ThriftSocketFileObjectInputStreamReader(socketInputStream);
			fileMessage = (SocketFile) objectReader.readObject();
		}
		catch (IOException | ClassNotFoundException e)
		{
			try
			{
				socket.close();
			}
			catch (IOException ex)
			{
				ServerLogger.log(Level.SEVERE, "File transfer failed.");
			}
			
			return;
		}
		
		switch (fileMessage.getAction())
		{
			case SocketFile.FILE_UPLOAD: 
			{
				FilePOJO filePOJO = new FilePOJO();
				filePOJO.setOwner(fileMessage.getOwner());
				filePOJO.setFileName(fileMessage.getFileName());
				filePOJO.setDirectory(fileMessage.getDirectoryName());
				filePOJO.setFileInputStream(socketInputStream);
				
				if(serverBLL.isExistingDirectory(fileMessage.getDirectoryName(), fileMessage.getOwner()))
				{
					try
					{
						ObjectOutputStream objectOutputStream = new ObjectOutputStream(socketOutputStream);
						objectOutputStream.flush();
						objectOutputStream.writeObject(new SocketReceip(SocketReceip.OK));
						objectOutputStream.flush();
					}
					catch (IOException e)
					{
						ServerLogger.log(Level.SEVERE, "File upload failed.");
					}
					
					int uploadStatus = -1;
					
					try
					{
						serverBLL.uploadFile(filePOJO);
						
						if(serverBLL.isExistingDirectory(fileMessage.getDirectoryName(), fileMessage.getOwner()))
						{
							uploadStatus = SocketReceip.OK;
						}
						else 
						{
							uploadStatus = SocketReceip.ERROR;
							serverBLL.deleteAllFileVersions(filePOJO);
						}
						
					}
					catch (FileTransferException | DataBaseAccessException | FileOperationException e)
					{
						uploadStatus = SocketReceip.ERROR;
						ServerLogger.log(Level.SEVERE, "File upload failed: " + fileMessage.getOwner() + " =>> " + fileMessage.getFileName());
					}
					
					try
					{
						ObjectOutputStream objectOutputStream = new ObjectOutputStream(socketOutputStream);
						objectOutputStream.flush();
						objectOutputStream.writeObject(new SocketReceip(uploadStatus));
						objectOutputStream.flush();
					}
					catch (IOException e)
					{
						ServerLogger.log(Level.SEVERE, "File upload failed: " + fileMessage.getOwner() + " =>> " + fileMessage.getFileName());
					}
				}
				else 
				{
					try
					{
						ObjectOutputStream objectOutputStream = new ObjectOutputStream(socketOutputStream);
						objectOutputStream.flush();
						objectOutputStream.writeObject(new SocketReceip(SocketReceip.ERROR));
						objectOutputStream.flush();
					}
					catch (IOException e)
					{
						ServerLogger.log(Level.SEVERE, "File upload failed: " + fileMessage.getOwner() + " =>> " + fileMessage.getFileName());
					}
				}
				
			}break;
			
			case SocketFile.FILE_DOWNLOAD:
			{
				FilePOJO filePOJO = new FilePOJO();
				filePOJO.setOwner(fileMessage.getOwner());
				filePOJO.setFileName(fileMessage.getFileName());
				filePOJO.setVersion(fileMessage.getFileVersion());
				filePOJO.setDirectory(fileMessage.getDirectoryName());
				filePOJO.setFileOutputStream(socketOutputStream);
				
				try
				{
					serverBLL.downloadFile(filePOJO);
				}
				catch (FileTransferException | DataBaseAccessException e)
				{
					ServerLogger.log(Level.SEVERE, "File download failed: " + fileMessage.getOwner() + " =>> " + fileMessage.getFileName());
				}
				
			}break;
		}
		
		try
		{
			socket.shutdownOutput();
			socket.shutdownInput();
			socketOutputStream.close();
			socketInputStream.close();
		}
		catch (IOException e)
		{
			try
			{
				socket.close();
			}
			catch (IOException ex)
			{
				ServerLogger.log(Level.SEVERE, "File transfer failed: " + fileMessage.getOwner() + " =>> " + fileMessage.getFileName());
			}
			
			return;
		}
		
		try
		{
			socket.close();
		}
		catch (IOException e)
		{
			ServerLogger.log(Level.SEVERE, "File transfer failed: " + fileMessage.getOwner() + " =>> " + fileMessage.getFileName());
		}
	}
}
